package net.jdm.domain.entity;

import java.util.Collection;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import net.jdm.common.HashSetExt;
import net.jdm.domain.caculator.CycleCaculator;
import net.jdm.domain.caculator.DistanceCaculator;
import net.jdm.domain.dependence.PackageDependence;

public class Component  implements ContainerNode,Scope{
	private final String name;
	private Component parent;
	private final HashSetExt<Package> packages = new HashSetExt<Package>();
	private HashSetExt<Component> subComponents = new HashSetExt<Component>();
	private HashSetExt<Component> afferents = new HashSetExt<Component>();
	private HashSetExt<Component> efferents = new HashSetExt<Component>();

	public Component(String name) {
		this.name=name;
	}

	@Override
	public String getName() {
		return this.name;
	}
	@Override
	public boolean isStable() {
		for(Package subPackage:getPackages()){
			if(!subPackage.isStable())
				return false;
		}
		return true;
	}

	public Package getRootPackage() {
		return packages.getByHashCode(this.name.hashCode());
	}

	@Override
	public HashSetExt<Component> getAfferents() {
		return this.afferents;
	}

	public void setAfferents(HashSetExt<Component> afferents) {
		this.afferents = afferents;
	}
	public boolean addAfferent(Component afferent) {
		return this.afferents.add(afferent);
	}
	@Override
	public HashSetExt<Component> getEfferents() {
		return this.efferents;
	}
	public void setEfferents(HashSetExt<Component> efferents) {
		this.efferents = efferents;
	}
	public boolean addEfferent(Component efferent) {
		return this.efferents.add(efferent);
	}

	public HashSetExt<Package> getPackages() {
		return this.packages;
	}

	public boolean addPackage(Package jpackage) {
		return this.packages.add(jpackage);
	}

	public boolean containsInnerPackageCycle() {
		return !getCyclesWithinMe().isEmpty();
	}
	public Set<PackageDependence> getDependencesWithinMe(){
		HashSetExt<PackageDependence> packageDependence = new HashSetExt<PackageDependence>();
		for (Package javaPackage : packages) {
			Set<Package> efferentPackages = javaPackage.getEfferents();
			for(Package efferentPackage:efferentPackages){
				if(packages.contains(efferentPackage)){
					packageDependence.add(new PackageDependence(javaPackage, efferentPackage));
				}
			}
		}
		return packageDependence;
	}
	public Collection<List<? extends ContainerNode>> getCyclesWithinMe() {
		Map<HashSetExt<Package>, List<? extends ContainerNode>> resultCycles = new HashMap<HashSetExt<Package>, List<? extends ContainerNode>>();
		Set<Package> javaPackages=new HashSet<Package>();
		javaPackages.addAll(getPackages());
		for(Package javaPackageExt:javaPackages){
			for(List<Package> cycle:javaPackageExt.getCycles()){
				HashSetExt<Package> key=new HashSetExt<Package>();
				key.addAll(cycle);
			    resultCycles.put(key,cycle);
			}
		}
		return resultCycles.values();
	}
	public Collection<List<Component>> getOuterComponentCycles(){
		List<List<Component>> result=new ArrayList<List<Component>>();
		for(List<ContainerNode> crycle:CycleCaculator.getInstance().getCycles(this)){
			List<Component> componentCrycle=new LinkedList<Component>();
			for(ContainerNode node:crycle){
				componentCrycle.add((Component)node);
			}
			result.add(componentCrycle);
		}
		return result;
	}
	public Collection<Class> getClasses() {
		Set<Class> classes = new HashSet<Class>();
		for (Package jpackage : getPackages()) {
			classes.addAll(jpackage.getClasses());
		}
		return classes;
	}

	@Override
	public int getClassCount() {
		return getClasses().size();
	}

	@Override
	public int getAbstractClassCount() {
		int count = 0;

		for (Class clazz : getClasses()) {
			if (clazz.isAbstract()) {
				count++;
			}
		}
		return count;
	}

	public int getConcreteClassCount() {
		int count = 0;

		for (Class clazz : getClasses()) {
			if (!clazz.isAbstract()) {
				count++;
			}
		}
		return count;
	}

	public int afferentCoupling() {
		return getAfferents().size();
	}

	public int efferentCoupling() {
		return getEfferents().size();
	}
	@Override
	public int afferentCouplingWeight() {
		return getWeight(getAfferents());
	}
	@Override
	public int efferentCouplingWeight() {
		return getWeight(getEfferents());
	}
	private int getWeight(Collection<Component> components) {
		int weight=0;
		for(Component afferent:components){
			weight+=(afferent.isStable()?0:1);
		}
		return weight;
	}
	@Override
	public float instability() {
		return DistanceCaculator.getInstance().instability(this);
	}

	@Override
	public float abstractness() {
		return DistanceCaculator.getInstance().abstractness(this);
	}

	@Override
	public float distance() {
		return DistanceCaculator.getInstance().distance(this);
	}
	public Component getParent() {
		return parent;
	}

	public void setParent(Component parent) {
		this.parent = parent;
	}

	public Set<Component> getSubComponents() {
		return subComponents;
	}

	public void setSubComponents(HashSetExt<Component> subComponents) {
		this.subComponents = subComponents;
	}

	public void addSubComponents(Component subComponent) {
		this.subComponents.add(subComponent);
	}

	@Override
	public boolean equals(Object other) {
		if (other instanceof Component) {
			Component otherComponent = (Component) other;
			return otherComponent.getName().equals(getName());
		}
		return false;
	}

	@Override
	public int hashCode() {
		return getName().hashCode();
	}

    @Override
	public String toString(){
    	return getName();
    }
}
